package com.zlx.mall.basic.resource;

import com.zlx.mall.basic.cache.RedissonDistributedLock;
import com.zlx.mall.basic.util.json.JacksonUtil;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.Duration;
import java.util.Optional;

/**
 * @author qingke
 **/
public abstract class AbstractResourceService implements IResourceService {

    @Resource
    private RedissonClient redissonClient;
    @Resource
    private RedissonDistributedLock redissonDistributedLock;
    @SuppressWarnings("unchecked")
    protected <T> T parse(String resourceValue, Class<T> tClass) {

        String trimmed = StringUtils.trim(resourceValue);

        try {

            if (String.class == tClass) {

                return (T) resourceValue;

            } else if (Boolean.class == tClass) {

                return (T) BooleanUtils.toBooleanObject(trimmed);

            } else if (Integer.class == tClass) {

                return (T) (isHexNumber(trimmed) ? Integer.decode(trimmed) : Integer.valueOf(trimmed));

            } else if (Long.class == tClass) {

                return (T) (isHexNumber(trimmed) ? Long.decode(trimmed) : Long.valueOf(trimmed));

            } else if (BigDecimal.class == tClass || Number.class == tClass) {

                return (T) new BigDecimal(trimmed);

            } else if (Duration.class == tClass) {

                return (T) Duration.parse(trimmed);

            } else {
                return JacksonUtil.toObject(resourceValue, tClass);
            }

        } catch (Exception e) {
            throw new IllegalArgumentException(
                    "Cannot convert String [" + resourceValue + "] to target class [" + tClass.getName() + "]", e);
        }
    }

    private boolean isHexNumber(String value) {
        int index = (value.startsWith("-") ? 1 : 0);
        return (value.startsWith("0x", index) ||
                value.startsWith("0X", index) ||
                value.startsWith("#", index));
    }

    @Override
    public <T> Optional<T> get(ResourceEnum resource, Class<T> tClass) {
        String value;

        String cacheKey = String.format(ResourceCacheConstants.CACHE_KEY_FORMAT, resource.getCode());
        RBucket<String> cache = redissonClient.getBucket(cacheKey, StringCodec.INSTANCE);

        if ((value = (cache.isExists() ? cache.get() : null)) == null) {

            String lockKey = String.format(ResourceCacheConstants.LOCK_KEY_FORMAT, resource.getCode());
            RLock lock = redissonDistributedLock.getLock(lockKey);

            if (redissonDistributedLock.tryLock(lock, ResourceCacheConstants.LOCK_LEASE_TIME_SECONDS)) {

                try {
                    if ((value = queryFromDatabase(resource)) != null) {
                        cache.set(value);
                    }
                } catch (Exception ignored) {
                    // ignored
                } finally {
                    redissonDistributedLock.unlock(lock);
                }
            }
        }

         return Optional.ofNullable(value).map(v -> parse(v, tClass));
    }

    @Override
    public <T> T get(ResourceEnum resource, Class<T> tClass, T defaultValue) {
        return get(resource, tClass).orElse(defaultValue);
    }

    /**
     * Query from database
     *
     * @param resource resource code enum
     * @return resource value
     */
    protected abstract String queryFromDatabase(ResourceEnum resource);
}
